home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 6 / MacMania 6.toast / / Multimedia & Desktop / VideoToolbox / VideoToolboxSources / Timer.c < prev    next >
C/C++ Source or Header  |  1997-05-30  |  26KB  |  640 lines

  1. /*
  2. Timer.c
  3.  
  4. Don't use Timer.c! Use Seconds.c instead.
  5.  
  6. NOTE:  Fran Miezin, fran@petcn.wustl.edu, writes (2/97) to report that repeatedly
  7. calling PeekTimer in a tight loop causes the Mac's clock to run fast, resulting
  8. in timing that is about 2% different from that measured with a stopwatch. On
  9. a 7100 (System 7.5.3) Fran found that calling PeekTimer distorted the times
  10. reported by Timer.c while TickCount was unaffected, but on a 7500 the
  11. TickCount rate was affected as well. My conclusion is that this represents yet
  12. another bug in Apple's Time Manager.
  13. Don't use it. Don't use this VideoToolbox Timer.c file, since it's based on
  14. Apple's Time Manager traps. Use the VideoToolbox Seconds.c instead;
  15. it's much easier to use, bug-free, and has no side effects.
  16.  
  17. NOTE: I just learned (6/95) of a handy Apple Toolbox routine called Microseconds(),
  18. which appeared with System 7, and is documented in Inside Mac: OS Utilities, 
  19. and in Minow's article in Develop,"Timing on the Macintosh"
  20. <http://devworld.apple.com/dev/techsupport/develop/issue26/minow.html>
  21. For most applications Microseconds() will be easier to use than Timer.c,
  22. and just as accurate. See the demo NoiseVBL.c for an example of its use.
  23. 1/97: In fact, there's an even better routine, UpTime, available on some machines.
  24. I recommend you just call the new VideoToolbox Seconds.c which uses the best
  25. available time routine.
  26.  
  27.  
  28. Timer.c is an interval timer based on Apple's Time Manager. It returns the time, in
  29. seconds, that elapsed between calling StartTimer() and PeekTimerSecs() or
  30. StopTimerSecs(). The timing of a single interval (from StartTimer to PeekTimer,
  31. PeekTimerSecs, StopTimer, or StopTimerSecs) is always very accurate (better than
  32. 1 part in 1000 relative to a good time standard) and a precision of better than
  33. 200 µs. Under System 7, which has the Extended Time Manager, (except on the
  34. Radius Rocket) this accuracy applies to all intervals; measuring splits by
  35. peeking has no effect on the accuracy of subsequent times. Under System 6 (or on
  36. the Radius Rocket even under System 7) each peek will lose a variable amount of
  37. time, compromising accuracy of subsequent times from that timer, due to
  38. documented deficiencies of Apple's old Revised Time Manager. The even older
  39. "Standard" Time Manager is not supported here, and will result in an error
  40. message. Note that you are allowed an unlimited number of timers that each
  41. operate independently. Peeking one timer has no effect on the rest of your
  42. timers.
  43.  
  44.     Timer *NewTimer(void);
  45.     void DisposeTimer(Timer *t);
  46.     void StartTimer(Timer *t);
  47.     double PeekTimerSecs(Timer *t);    // s, with µs precision and no time limit
  48.     double StopTimerSecs(Timer *t);    // s, with µs precision and no time limit
  49.     long PeekTimer(Timer *t);    // µs, up to 36 minutes
  50.     long StopTimer(Timer *t);    // µs, up to 36 minutes
  51.  
  52.     Timer *timer;
  53.     long t;
  54.     
  55.     timer=NewTimer();
  56.     StartTimer(timer);
  57.     do(i=0;i<100;i++);
  58.     t=StopTimer(timer);
  59.     DisposeTimer(timer);
  60.     printf("One hundred iterations takes %ld µs\n",t);
  61.  
  62. The timing result comes in two flavors. StopTimerSecs() and PeekTimerSecs() both
  63. return the time in secs as a double, and can time an interval of essentially
  64. unlimited duration with 200 µs precision. StopTimer() and PeekTimer() return the
  65. time in microseconds (with same 200 µs precision) as a long, which can hold a
  66. time up to 2,147,483,647 µs, which is nearly 36 minutes. I generally prefer
  67. PeekTimerSecs, because it's foolproof (won't overflow), even though it's a bit
  68. slower than PeekTimer. But if you don't have a floating point unit, then
  69. PeekTimerSecs is MUCH slower than PeekTimer.
  70.  
  71. You can have an unlimited number of Timers running at once. The only restriction
  72. is that you must create each Timer, by calling NewTimer() before you use it,
  73. and, obviously, should not use it after calling DisposeTimer().
  74.  
  75. The Time Manager seems to use interrupts at a higher rate than the 10 s interval
  76. I requested here (which would never expire in typical use). I infer this from
  77. the fact that disabling interrupts by SetPriority(7) greatly reduces the timing
  78. value returned by StopTimer(). So don't disable interrupts while you're timing.
  79.  
  80. StopTimer() adds a small offset (about 62 µs on a Mac IIci) to the raw time in
  81. order to return an unbiased estimate of the time from when StartTime was called
  82. to when StopTime() was called. This is the most useful time measure for
  83. measuring the interval between two events (e.g. video frames). Alternatively, if
  84. you wish to measure processing time, then it is more useful to compute the time
  85. from when StartTimer() returns to when StopTimer() is called. For this you
  86. simply subtract the fixed duration of StartTimer(), from call to return, which is
  87. provided in µs in your Timer structure in the long structure member
  88. "timeToStartTimer".
  89.  
  90.     betweenTime = StopTimer(t) - t->timeToStartTimer;
  91.     
  92. When you use StopTimerSecs() you'd have to divide timeToStartTimer by a
  93. million to convert µs to s. For some purposes this offset is negligibly small.
  94. On my Mac IIci the timeToStartTimer is 240 µs. This offset, and the one
  95. mentioned above are measured automatically the first time you call NewTimer().
  96. Alternatively, instead of having to remember the name of the structure member,
  97. just measure the timeToStartTimer yourself and subtract it from all subsequent
  98. measures:
  99.  
  100.     StartTimer(t);
  101.     s0=StopTimerSec(t);
  102.  
  103. The Timer structures are kept in a linked list so that KillEveryTimer(), which
  104. is placed in the _atexit() queue, will find and kill them when the application
  105. exits.
  106.  
  107. TEST PROGRAM:
  108.  
  109. Measure the same interval in three different ways. All three answers agree, at
  110. least under System 7. Note that timer2 is repeatedly peeked at, while "timer" is
  111. uninterrupted. This checks for any side effects of peeking. (There will be a
  112. substantial loss of time due to peeking if you run without the Extended Time Mgr,
  113. i.e. under System 6 or on Radius Rocket.)
  114.  
  115. void main(void)
  116. {
  117.     double s,s2;
  118.     long ticks,m,m2;
  119.     int i,n=1000;
  120.     Timer *timer,*timer2;
  121.     
  122.     printf("We'll simultaneously measure a single time interval in 3 ways:\n"
  123.         "1. by just starting and stopping the timer\n"
  124.         "2. by starting the timer and repeatedly peeking at it %d times\n"
  125.         "3. by using TickCount(), which runs at 60.15 Hz.\n...\n",n);
  126.     timer=NewTimer();
  127.     timer2=NewTimer();
  128.     Delay(1,&ticks);    // synchronize ourselves to the tick counter for better accuracy.
  129.     StartTimer(timer);
  130.     StartTimer(timer2);
  131.     for(i=0;i<n;i++)s2=PeekTimerSecs(timer2);    // time PeekTimerSecs()
  132.     ticks=TickCount()-ticks;
  133.     s=StopTimerSecs(timer);
  134.     s2=StopTimerSecs(timer2);
  135.     printf("The interval was:\n1. %4.0f ms\n2. %4.0f ms\n3. %4.0f±%.0f ms\n\n"
  136.         ,s*1e3,s2*1e3,1e3*(ticks+0.5)/60.15,1e3*0.5/60.15);
  137.     printf("Each call to PeekTimerSecs() takes %4.0f µs, and loses %4.0f µs.\n"
  138.         ,s*1e6/n,(s-s2)*1e6/n);
  139.     StartTimer(timer);
  140.     StartTimer(timer2);
  141.     for(i=0;i<n;i++)m2=PeekTimer(timer2);    // time PeekTimer()
  142.     m=StopTimer(timer);
  143.     m2=StopTimer(timer2);
  144.     printf("Each call to PeekTimer()     takes %4ld µs, and loses %4ld µs.\n"
  145.         ,m/n,(m-m2)/n);
  146.     DisposeTimer(timer);
  147.     DisposeTimer(timer2);
  148. }
  149.  
  150. ACKNOWLEDGMENT:
  151. David Brainard wrote the first draft of PeekTimer().
  152.  
  153. APPLE DOCUMENTION ERRORS:
  154.  
  155. 9/6/96. I've just learned from Eric Simenel at Apple, that these two points 
  156. will be addressed, authoritatively, in his Tech Note 1063.
  157. <http://devworld.apple.com/dev/technotes/tn/tn1063.html>
  158.  
  159. There are two errors in the Time Manager chapter of "New Inside
  160. Macintosh: Processes". One of the errors is particularly relevant to the
  161. PowerPC. Both errors are on page 3-8.
  162.  
  163. 1. The manual says, "You should set tmWakeUp and tmReserved to 0 when you first
  164. install an extended Time Manager task." That's correct, but it should go on to
  165. say that "You should set tmReserved to 0 before each and every call to
  166. InsXTime." (This is now confirmed by Apple, see 7/31/96 note below from DEVSUPPORT.)
  167.  
  168. It took me a long time to discover this. Some computers, e.g. my PowerBook 170,
  169. don't care. But, e.g. the PowerMac 6100, does care. It increments tmReserved
  170. each time a task is installed by InsXTime. If tmReserved reaches 128 then the
  171. Time Manager goes haywire. Conversely, everything runs fine on every Mac I've
  172. tested if tmReserved is zeroed before each call to InsXTime.
  173.  
  174. 2. The manual says "The tmWakeUp field contains the time at which the Time
  175. Manager task specified by tmAddr was last executed (or 0 if it has not yet been
  176. executed)." THIS IS WRONG AND MISLEADING! It should say, instead, "The tmWakeUp
  177. field contains the time at which the Time Manager task specified by tmAddr is
  178. scheduled to be executed (or 0 if it has not yet been scheduled)."
  179.  
  180. Subject:     Apple DTS acks bug you documented in Timer.c
  181. From:        Sigurdur Asgeirsson, sigurasg@menandmice.com
  182. To:          denis@psych.nyu.edu
  183.  
  184. While implementing a fixed frequency Time Manager Task in my program I
  185. came across a situation where my internal time would periodically pass very
  186. very quickly. Remembering that there was some kind of timer service in the
  187. VideoToolBox sources, I grabbed them off the net (after a three day
  188. debugging session, mind you :-(), and had a look at your code. I then
  189. modified my code to set tmReserved to 0 before every InsXTime and wrote to
  190. DTS. What follows is their response, I'm not sure if this message may be
  191. redistributed without Mr. Eric Simenel's prior consent, but at least here
  192. is confirmation on the Time Manager bug.
  193.  
  194. Date: 31 Jul 1996 
  195. From: "DEVSUPPORT" <devsupport@apple.com>
  196. To: "Apple Developer" <DEVELOPER@MENANDMICE.COM>
  197.  
  198. Reference #191859
  199. Hello Sigurdur,
  200.  
  201. You're right and Denis G. Pelli, the author of Video-Toolbox is right too.
  202.  
  203. Back in October '92, a modification was done to the Time Mgr and has never
  204. been documented...
  205.  
  206. I'm not authorized to divulge too many details but I can say this:
  207.  
  208. • This non-documented modification has been running since October '92 and
  209. it has never been modified ever since, so I'd say that it's here to stay a
  210. long time (I can't guarantee FOREVER since that's too long a time...).
  211.  
  212. • After 127 InsXTime/PrimeTime calls, the Extended Time Task is converted
  213. in a non-extended Time Task, and since it is being called with a 0 delay,
  214. it's no surprise your internal time seems to pass very very quickly...
  215.  
  216. • If you set tmReserved to 0 before each InsXTime, this never happens.
  217.  
  218. In fact, the InsXTime call doesn't do a thing with tmReserved, it's
  219. PrimeTime which deals with it, and the modification is in PrimeTime.
  220.  
  221. Since it has never been documented before (except by Denis G. Pelli), I
  222. believe I will write a Tech Note about it. Thank you for signaling this to
  223. us.
  224.  
  225. Eric Simenel
  226. Developer Support Center
  227.  
  228.  
  229. RADIUS ROCKET BUG:
  230. This applies to my Radius Rocket in my Mac II using RocketWare 1.5 running under
  231. System 7.1 with System Update.
  232.  
  233. Apparently Radius decided not to implement the Extended Time Manager and only
  234. went so far as diverting the InsXTime trap to InsTime. A minimal fix would be to
  235. at least give honest information by making Gestalt correctly report the Time
  236. Manager version as gestaltRevisedTimeMgr instead of falsely claiming it's
  237. gestaltExtendedTimeMgr. My programs check for this, and I spent a whole day
  238. debugging crashes on my Radius due to this misleading information from Gestalt.
  239.  
  240. According to Gestalt, the Extended Time Manager is present, but in fact the
  241. InsXTime trap is only providing the service expected from the InsTime trap of
  242. the Revised Time Manager. Specifically, in the program below, when we call
  243. InsXTime the second time, with a nonzero tmWakeUp, we are supposed to resume the
  244. previous interval, not start a new one. Since the original interval was 100 s,
  245. there should still be lots of time left in it, and when we call RmvTime for the
  246. last time tmCount should be nonzero. This program fails only on my Radius
  247. Rocket. It runs fine on my Mac II, IIci, IIfx, LC475, Quadra 840av, 
  248. PowerBook 170, and PowerMac.
  249.  
  250. UNDERSTANDING THE CODE OF TIMER.C:
  251. There are a few subtleties to understanding the code. Firstly, you should read
  252. the manual, i.e. the Time Manager chapter of New Inside Mac: Processes. If the
  253. Extended Time Manager is available, PeekTimer takes advantage of its ability to
  254. resume the interrupted interval, so the splits (from PeekTimer and
  255. PeekTimerSecs) won't have any effect on the accuracy of subsequent times.
  256. However, there is a tricky case that requires special handling. If we are very
  257. close to the end of the current interval then the interval might expire before
  258. we have a chance to call PrimeTime(). As explained in the manual on page 3-8
  259. this would amount to an impossible request for a task execution time in the
  260. past. When that happens the Time Mgr changes the the requested time to the
  261. present, which would destroy our synchrony. Therefore PeekTimer checks that
  262. there is at least 10 ms left in the interrupted interval, and, if not, then
  263. advances to the next interval, incrementing the counters appropriately as though
  264. our timer task had been called. This transparently preserves accuracy.
  265.  
  266. HISTORY:
  267. 8/19/92 dgp    based on Time Manager chapter in Inside Mac VI and TimeIt.c, 
  268.     which is now obsolete. I also benefited from examining code by Jonothan Kolodny
  269.     forwarded to me by Thomas Busey.
  270. 8/27/92    dgp    Rewrote everything. Made the interrupt service routine reentrant 
  271.     by eliminating all use of global variables, using only the structure pointed
  272.     to by A1. There can now be an unlimited number of timers active at once.
  273.     Added NewTimer() and DisposeTimer() to manage them. 
  274. 9/10/92    dgp    added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
  275.     Memory book this isn't strictly necessary, since Time Manager tasks will be
  276.     called only when it's safe.
  277. 1/11/93 dgp StopTimerSecs() now returns NAN if called with a NULL pointer.
  278. 7/9/93    dgp    Test MATLAB in if() instead of #if. 
  279. 5/28/94 dgp Made compatible with Apple's Universal Headers. Thanks to Bob Dougherty 
  280. (wolfgang@cats.ucsc.edu) for reporting the incompatibility.
  281. 9/7/94    dgp    Made declaration of TimerTask--how it receives the argument ptr--conditional on PowerPC.
  282. 10/22/94 dgp Added PeekTimer, written by David Brainard, and enhanced it to used the Extended
  283. Time Mgr's facility of resuming an interrupted interval. Modularized all the code somewhat,
  284. eliminating duplications.
  285. 10/24/94 dgp Made declaration of GetA0() explicitly indicate that answer is returned in D0, 
  286. for better compatibility with Metrowerks CodeWarrior C.
  287. 10/27/94 dgp It is very annoying for the machine to crash anytime you try to quit your application
  288. by escaping via MacsBugs escape to shell. The problem is that CodeWarrior 4.5 doesn't attach
  289. the atexit() tasks to _EscapeToShell. So I do it here. 
  290. 11/3/94 dgp In response to a bug report by David Brainard that after 128 calls to PeekTimer things
  291. got screwy I replicated the problem and discovered that I could cure it by clearing tmReserved
  292. in ResumeTimer below, though I have no idea why this is necessary.
  293. 11/4/94 dgp After discovering that the Radius Rocket lies, via Gestalt, claiming that
  294. the Extended Time Manager is present when it really isn't, I added code to check for
  295. the services of the Extended Time Manager, and adjust t->timeManagerVersion accordingly.
  296. This eliminates the crashes I was getting when running PeekTimer on the Radius Rocket.
  297. 11/5/94 dgp tested and debugged the code when running without the Extended Time Manager.
  298. 1/5/95    dgp made compatible with Universal Headers 2.
  299. 5/3/95 dgp fixed test for presence of VM, which before was always returning false.
  300. 6/6/95 dgp in response to report from David Brainard, PeekTimer now returns LONG_MIN, like
  301. StopTimer(), when supplied a NULL pointer.
  302. 7/1/95 dgp just call AtExitToShell(), without any conditionals. Special cases, e.g. MATLAB,
  303. are handled in AtExitToShell.c.
  304. 9/5/96    dgp added Apple's confirmation, forwarded by Sigurdur Asgeirsson, sigurasg@menandmice.com,
  305. of the undocumented need to keep zeroing the tmReserved field.
  306. 5/2/97    dgp Save/restore A4/5 as appropriate. Our timer task doesn't in fact use globals, so this
  307. is superfluous, but I thought I'd add the code now, since I just figured out how to do it.
  308. */
  309. #include "VideoToolbox.h"
  310. #if GENERATINGPOWERPC
  311.     static pascal void TimerTask(Timer *t);
  312. #else
  313.     static pascal void TimerTask(void);
  314. #endif
  315. void KillEveryTimer(void);
  316. #define CODE_RESOURCE MATLAB
  317.  
  318. /* This is a copy for reference. Original is in VideoToolbox.h.
  319. struct Timer{
  320.     TMTask time;
  321.     long ourA5;
  322.     long interval;                    // interval length (in negative µs)
  323.     long elapsed;                    // extra time, (in -µs), beyond elapsedIntervals*interval
  324.     long elapsedIntervals;            // positive count
  325.     long timeToStartTimer;            // minimum time in µs
  326.     long stopDelay;                    // µs from call to stop, re from call to start
  327.     long timeManagerVersion;
  328.     struct Timer *next,*previous;    // doubly linked list of Timers
  329. };
  330. */
  331.  
  332. static Timer defaultTimer,qTimer;
  333. static long vmPresent=0,pageSize=0;
  334. #define TASK_SIZE 1024    // Generous guess for size of routine
  335. static double TimerSecs(Timer *t);
  336. static void ResumeTimer(Timer *t);
  337. static long TimerMicroSecs(Timer *t);
  338. #if !defined(NewTimerProc)
  339.     #define NewTimerProc(proc)     (TimerProcPtr)proc
  340. #endif
  341.  
  342. Timer *NewTimer(void)
  343. {
  344.     static short firstTime=1;
  345.     Timer *t,*tt;
  346.     long j;
  347.     
  348.     if(firstTime){
  349.         firstTime=0;
  350.         qTimer.next=qTimer.previous=NULL;
  351.         AtExitToShell(KillEveryTimer);
  352.         Gestalt(gestaltVMAttr,&vmPresent);
  353.         vmPresent &= 1L<<gestaltVMPresent;
  354.         if(vmPresent)Gestalt(gestaltLogicalPageSize,&pageSize);
  355.         t=&defaultTimer;
  356.         #if GENERATING68K
  357.             #if CODE_RESOURCE
  358.                 t->ourA5 = GetA4();
  359.             #else
  360.                 t->ourA5 = SetCurrentA5();
  361.             #endif
  362.         #endif
  363.         t->time.tmAddr = NewTimerProc(TimerTask);
  364.         t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  365.         t->elapsedIntervals=t->elapsed=0;                            
  366.         t->timeManagerVersion=0;
  367.         Gestalt(gestaltTimeMgrVersion,&t->timeManagerVersion);
  368.         if(t->timeManagerVersion>=gestaltExtendedTimeMgr){
  369.             // Let's make sure the Extended Time Manager is really here.
  370.             // Under System 7.1 the Radius Rocket claims it's present, but it's not.
  371.             TMTask time;
  372.  
  373.             time.tmAddr = NULL;
  374.             time.tmCount=time.tmWakeUp=time.tmReserved=0;
  375.             InsXTime((QElemPtr)&time);
  376.             PrimeTime((QElemPtr)&time,-100000000L);    // interval of 100 s
  377.             RmvTime((QElemPtr) &time);
  378.             time.tmReserved=0;                        // undocumented, but necessary on some Macs
  379.             InsXTime((QElemPtr)&time);
  380.             PrimeTime((QElemPtr)&time,0);            // resume interrupted interval
  381.             RmvTime((QElemPtr) &time);
  382.             // a tmCount of zero couldn't happen if the Extended Time Manager were present.
  383.             if(time.tmCount==0)t->timeManagerVersion=gestaltRevisedTimeMgr;
  384.         }
  385.         switch(t->timeManagerVersion){
  386.         case 0:
  387.         case gestaltStandardTimeMgr:
  388.             printf("NewTimer: old System lacks the Revised Time Manager. %s\n",__FILE__);
  389.             return NULL;
  390.         case gestaltRevisedTimeMgr:
  391.         case gestaltExtendedTimeMgr:
  392.         default:
  393.             // The programs in Timer.c assume that t->interval<0
  394.             t->interval = -10000000;        // Set the timer interval to 10 s
  395.         }
  396.         t->next=NULL;
  397.         t->previous=&qTimer;
  398.         t->timeToStartTimer=t->stopDelay=0;
  399.         
  400.         // Measure timeToStartTimer and stopDelay offsets.
  401.         t=NewTimer();
  402.         StartTimer(t);
  403.         t->timeToStartTimer=StopTimer(t);
  404.         tt=NewTimer();
  405.         StartTimer(t);
  406.         StartTimer(tt);
  407.         j=StopTimer(t);
  408.         DisposeTimer(tt);
  409.         t->stopDelay=2*t->timeToStartTimer-j;
  410.         // The computed "cycle" interval will have stopDelay removed.
  411.         // The user wishing to compute the "between" interval will be
  412.         // subtracting the timeToStartTimer, so we should subtract 
  413.         // the stopDelay from that so the stopDelay cancels out when 
  414.         // the "between" time is computed.
  415.         t->timeToStartTimer-=t->stopDelay;
  416.         defaultTimer.timeToStartTimer=t->timeToStartTimer;
  417.         defaultTimer.stopDelay=t->stopDelay;
  418.         return t;
  419.     }
  420.     t=(Timer *)NewPtr(sizeof(Timer));
  421.     if(t!=NULL){
  422.         *t=defaultTimer;
  423.         t->next=qTimer.next;
  424.         if(t->next!=NULL)t->next->previous=t;
  425.         qTimer.next=t;
  426.         if(vmPresent){
  427.             HoldMemory(t,sizeof(*t));
  428.             if(t->time.tmAddr!=NULL)HoldMemory(t->time.tmAddr,TASK_SIZE);
  429.         }
  430.         if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
  431.         else InsTime((QElemPtr)t);
  432.     }
  433.     return t;
  434. }
  435.  
  436. void DisposeTimer(Timer *t)
  437. {
  438.     if(t==NULL)return;
  439.     RmvTime((QElemPtr)t);
  440.     if(vmPresent){
  441.         //UnholdMemory(t,sizeof(*t));
  442.         if(qTimer.next==t && t->next==NULL && t->time.tmAddr!=NULL)
  443.             UnholdMemory(t->time.tmAddr,TASK_SIZE);
  444.     }
  445.     t->previous->next=t->next;
  446.     if(t->next!=NULL)t->next->previous=t->previous;
  447.     DisposPtr((Ptr)t);
  448. }
  449.  
  450. void StartTimer(Timer *t)
  451. {
  452.     if(t==NULL)return;
  453.     if(t->time.qType<0)StopTimer(t);    // whoops, it's already active.        
  454.     PrimeTime((QElemPtr)t,t->interval);
  455. }
  456.  
  457. static void ResumeTimer(Timer *t)
  458. {
  459.     // As noted above, it is important to clear tmReserved.
  460.     // The Time Manager documentation only says that tmReserved should be zeroed 
  461.     // when you FIRST install your task, not EACH TIME you install it.
  462.     // At least some computers (e.g. PowerMac) increment tmReserved each time PeekTimer
  463.     // is called, and things get screwy if tmReserved>=128. 
  464.     t->time.tmReserved=0;
  465.     if(t->timeManagerVersion>=gestaltExtendedTimeMgr){
  466.         // The Extended Time Mgr (System 7) allows us to resume exactly where we left off
  467.         // provided we don't clear t->time.tmWakeUp
  468.         if(t->time.tmCount==0 || t->time.tmCount<0 && t->time.tmCount>-10000){
  469.             // we're dangerously close to the end of this interval: skip to next interval
  470.             t->elapsedIntervals++;
  471.             InsXTime((QElemPtr)t);
  472.             PrimeTime((QElemPtr)t,t->interval);        // resume, skipping current interval's wakeup
  473.         }else{
  474.             InsXTime((QElemPtr)t);
  475.             PrimeTime((QElemPtr)t,0);                // resume interrupted interval
  476.         }
  477.     }else{
  478.         // Before System 7 we must start a new interval
  479.         t->elapsed+=t->interval;
  480.         if(t->time.tmCount<0)t->elapsed-=t->time.tmCount;
  481.         else t->elapsed+=t->time.tmCount*1000;
  482.         if(t->elapsed<t->interval){
  483.             // avoid overflow of t->elapsed
  484.             t->elapsed-=t->interval;
  485.             t->elapsedIntervals++;
  486.         }
  487.         t->time.qType=t->time.tmReserved=t->time.tmWakeUp=t->time.tmCount=0;
  488.         InsTime((QElemPtr)t);
  489.         PrimeTime((QElemPtr)t,t->interval);        // resume timing
  490.     }
  491. }
  492.  
  493. static double TimerSecs(register Timer *t)
  494. {
  495.     register double s;
  496.  
  497.     // add up the elapsed time plus the interval we're in, minus the time left in it
  498.     s=-((double)(t->elapsedIntervals+1)*t->interval + t->elapsed);// µs
  499.     if(t->time.tmCount>0) s-=t->time.tmCount*1000;        // -µs until end of interval
  500.     else s+=t->time.tmCount;                            // -µs until end of interval
  501.     s-=t->stopDelay;                                    // compute "cycle" time
  502.     s*=0.000001;                                        // µs to s
  503.     return s;
  504. }
  505.  
  506. static long TimerMicroSecs(register Timer *t)
  507. {
  508.     register long microSecs;
  509.  
  510.     // add up the elapsed time plus the interval we're in, minus the time left in it
  511.     microSecs=-((t->elapsedIntervals+1)*t->interval + t->elapsed);// µs
  512.     if(t->time.tmCount>0) microSecs-=t->time.tmCount*1000;        // -µs until end of interval
  513.     else microSecs+=t->time.tmCount;                            // -µs until end of interval
  514.     microSecs-=t->stopDelay;                                    // compute "cycle" time
  515.     return microSecs;
  516. }
  517.  
  518. double PeekTimerSecs(Timer *t)
  519. {
  520.     Timer tCopy;
  521.  
  522.     if(t==NULL)return NAN;
  523.     RmvTime((QElemPtr)t);
  524.     tCopy=*t;    // get the info and resume timing as quickly as possible
  525.     ResumeTimer(t);
  526.     return TimerSecs(&tCopy);
  527. }
  528.  
  529. long PeekTimer(Timer *t)
  530. {
  531.     Timer tCopy;
  532.  
  533.     if(t==NULL)return LONG_MIN;
  534.     RmvTime((QElemPtr)t);
  535.     tCopy=*t;    // get the info and resume timing as quickly as possible
  536.     ResumeTimer(t);
  537.     return TimerMicroSecs(&tCopy);
  538. }
  539.  
  540. long StopTimer(Timer *t)    // Returns µs
  541. {
  542.     long microSecs;
  543.  
  544.     if(t==NULL)return LONG_MIN;
  545.     RmvTime((QElemPtr)t);
  546.     microSecs=TimerMicroSecs(t);
  547.     
  548.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  549.     t->time.qType=t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  550.     t->elapsed=t->elapsedIntervals=0;                            
  551.     if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
  552.     else InsTime((QElemPtr)t);
  553.     
  554.     return microSecs;
  555. }
  556.  
  557. double StopTimerSecs(Timer *t)                        // Returns secs
  558. {
  559.     double s;
  560.  
  561.     if(t==NULL)return NAN;
  562.     RmvTime((QElemPtr)t);
  563.     s=TimerSecs(t);
  564.     
  565.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  566.     t->time.qType=t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  567.     t->elapsed=t->elapsedIntervals=0;                            
  568.     if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
  569.     else InsTime((QElemPtr)t);
  570.     
  571.     return s;
  572. }
  573.  
  574. // KillEveryTimer turns off all our Timers before we quit. There is no need to
  575. // free the space since the system will do that automatically when the application terminates.
  576.  
  577. void KillEveryTimer(void)
  578. {
  579.     Timer *t;
  580.     extern Timer qTimer;
  581.  
  582.     t=qTimer.next;
  583.     while(t!=NULL){
  584.         RmvTime((QElemPtr)t);
  585.         if(vmPresent){
  586.             UnholdMemory(t,sizeof(*t));
  587.             if(t->time.tmAddr!=NULL)UnholdMemory(t->time.tmAddr,TASK_SIZE);
  588.         }
  589.         t=t->next;
  590.     }
  591. }
  592.  
  593. // On 68k machines the Revised & Extended Time managers set A1=&task.time before calling TimerTask
  594. // The code allowing access to globals is commented out because it is not needed here.
  595.  
  596. #if (THINK_C || THINK_CPLUS || SYMANTEC_C)
  597.     #pragma options(!profile)    // it would be dangerous to call the profiler from here
  598. #endif
  599. #if __MWERKS__
  600.     #pragma profile off            // on 68k it would be dangerous to call the profiler from here
  601. #endif
  602. #if GENERATING68K
  603.     #pragma parameter __D0 GetA1()
  604.     long GetA1(void)=0x2009;    // MOVE.L A1,D0
  605.     #if THINK_C
  606.         #pragma parameter __D0 SetA4(__D0)
  607.         long SetA4(long) = 0xC18C;    // EXG D0,A4
  608.     #else
  609.         long SetA4(long:__D0):__D0 = 0xC18C;    // EXG D0,A4
  610.     #endif
  611. #endif
  612.  
  613. #if GENERATINGPOWERPC
  614.     static pascal void TimerTask(Timer *t)
  615. #else
  616.     static pascal void TimerTask(void)            // Called at interrupt time
  617. #endif
  618.     {
  619.     #if GENERATING68K
  620.         long oldA;
  621.         Timer *t;
  622.  
  623.         t=(Timer *)GetA1();
  624.         #if CODE_RESOURCE
  625.             oldA = SetA4(t->ourA5);            // Reestablish A4 for global variables
  626.         #else
  627.             oldA = SetA5(t->ourA5);            // Reestablish A5 for global variables
  628.         #endif
  629.     #endif
  630.     PrimeTime((QElemPtr)t,t->interval);        // Repeat the interval
  631.     t->elapsedIntervals++;
  632.     #if GENERATING68K
  633.         #if CODE_RESOURCE
  634.             SetA4(oldA);                     // Restore A4
  635.         #else
  636.             SetA5(oldA);                     // Restore A5
  637.         #endif
  638.     #endif
  639. }
  640.